/**
 * 
 */
package hawk.example;

import static org.junit.platform.commons.support.AnnotationSupport.findAnnotatedFields;

import java.lang.reflect.Field;
import java.util.function.Predicate;

import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.api.extension.ParameterResolutionException;
import org.junit.jupiter.api.extension.ParameterResolver;
import org.junit.platform.commons.support.ModifierSupport;

/**
 * @author Hawk
 *
 */
public class RandomNumberExtension implements BeforeAllCallback, BeforeEachCallback, ParameterResolver{
	
	private final java.util.Random random = new java.util.Random(System.nanoTime());

	@Override
	public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
		return parameterContext.isAnnotated(Random.class) && isInteger(parameterContext.getParameter().getType());
	}

	@Override
	public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
		return this.random.nextInt();
	}

	/**
	 * Inject a random integer into non-static fields that are annotated with
	 * {@code @Random} and can be assigned an integer value.
	 */
	@Override
	public void beforeEach(ExtensionContext context) throws Exception {
		Class<?> testClass = context.getRequiredTestClass();
		Object testInstance = context.getRequiredTestInstance();
		injectFields(testClass, testInstance, ModifierSupport::isNotStatic);
	}
	

	/**
	 * Inject a random integer into static fields that are annotated with
	 * {@code @Random} and can be assigned an integer value.
	 */
	@Override
	public void beforeAll(ExtensionContext context) throws Exception {
		Class<?> testClass = context.getRequiredTestClass();
		injectFields(testClass, null, ModifierSupport::isStatic);
	}
	
	private void injectFields(Class<?> testClass, Object testInstance, Predicate<Field> predicate) {
		predicate = predicate.and(field -> isInteger(field.getType()));
		findAnnotatedFields(testClass, Random.class, predicate)
		.forEach(field -> {
			try {
				field.setAccessible(true);
				field.set(testInstance, this.random.nextInt());
			}
			catch (Exception ex) {
				throw new RuntimeException(ex);
			}
		});
	}
	
	
	private static boolean isInteger(Class<?> type) {
		return type == Integer.class || type == int.class;
	}

}
